"""
C++ template analyzer.

Provides comprehensive analysis of C++ templates including declarations,
instantiations, SFINAE patterns, and metaprogramming constructs.
"""

from typing import Dict, List, Any, Optional, Set, Tuple, Union
from dataclasses import dataclass, field
from collections import defaultdict
import re
import logging

from ...core.error_handler import log_debug, log_info


@dataclass
class TemplateParameter:
    """Represents a template parameter."""
    name: str
    kind: str  # type, non_type, template
    default_value: Optional[str] = None
    constraints: List[str] = field(default_factory=list)
    location: Dict[str, Any] = field(default_factory=dict)
    is_variadic: bool = False
    is_concept: bool = False  # C++20 concepts


@dataclass
class TemplateDeclaration:
    """Represents a template declaration."""
    name: str
    qualified_name: str
    kind: str  # function_template, class_template, variable_template, alias_template
    parameters: List[TemplateParameter] = field(default_factory=list)
    location: Dict[str, Any] = field(default_factory=dict)
    namespace: List[str] = field(default_factory=list)
    specializations: List[str] = field(default_factory=list)
    instantiations: List[str] = field(default_factory=list)
    is_partial_specialization: bool = False
    specialization_args: List[str] = field(default_factory=list)
    requires_clause: Optional[str] = None  # C++20 requires clause


@dataclass 
class TemplateInstantiation:
    """Represents a template instantiation."""
    template_name: str
    instantiation_args: List[str] = field(default_factory=list)
    location: Dict[str, Any] = field(default_factory=dict)
    context: str = ""  # Where the instantiation occurs
    is_explicit: bool = False
    is_deduced: bool = False
    instantiation_kind: str = "implicit"  # implicit, explicit, partial


@dataclass
class SFINAEPattern:
    """Represents a SFINAE (Substitution Failure Is Not An Error) pattern."""
    pattern_type: str  # enable_if, void_t, is_detected, etc.
    location: Dict[str, Any] = field(default_factory=dict)
    condition: str = ""
    template_context: str = ""
    used_traits: List[str] = field(default_factory=list)


class TemplateAnalyzer:
    """
    Analyzes C++ templates and metaprogramming constructs.
    
    Provides comprehensive analysis of template declarations, instantiations,
    specializations, SFINAE patterns, and C++20 concepts.
    """
    
    def __init__(self):
        """Initialize the template analyzer."""
        self.reset()
        
        # Known SFINAE patterns
        self.sfinae_patterns = {
            'enable_if': r'std::enable_if(?:_t)?<([^>]+)>',
            'void_t': r'(?:std::)?void_t<([^>]+)>',
            'is_detected': r'(?:std::experimental::)?is_detected(?:_v)?<([^>]+)>',
            'decltype': r'decltype\(([^)]+)\)',
            'has_member': r'(?:has_\w+|is_\w+)<([^>]+)>',
            'conditional': r'std::conditional(?:_t)?<([^>]+)>'
        }
        
        # Known type traits
        self.type_traits = {
            'is_same', 'is_convertible', 'is_base_of', 'is_derived_from',
            'is_constructible', 'is_assignable', 'is_copy_constructible',
            'is_move_constructible', 'is_integral', 'is_floating_point',
            'is_pointer', 'is_reference', 'is_array', 'is_function',
            'is_member_pointer', 'is_class', 'is_union', 'is_enum',
            'is_trivial', 'is_pod', 'is_empty', 'is_polymorphic',
            'is_abstract', 'is_final', 'has_virtual_destructor'
        }
        
        log_debug("Initialized C++ template analyzer")
    
    def reset(self):
        """Reset analyzer state for new analysis."""
        self.template_declarations = []
        self.template_instantiations = []
        self.template_specializations = []
        self.sfinae_patterns = []
        self.concept_declarations = []
        self.requires_clauses = []
        
        # Analysis caches
        self.template_index = {}
        self.instantiation_cache = defaultdict(list)
        self.metaprogramming_patterns = []
        
        # Statistics
        self.stats = {
            'template_declarations': 0,
            'template_instantiations': 0,
            'explicit_specializations': 0,
            'partial_specializations': 0,
            'sfinae_uses': 0,
            'concept_uses': 0,
            'metaprogramming_patterns': 0
        }
    
    def analyze_templates(self, ast_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Analyze all templates in the AST data.
        
        Args:
            ast_data: Complete AST data from parser
            
        Returns:
            Comprehensive template analysis results
        """
        self.reset()
        
        log_info("Starting C++ template analysis")
        
        # Extract template declarations from functions and classes
        self._extract_template_declarations(ast_data)
        
        # Analyze template instantiations
        self._analyze_template_instantiations(ast_data)
        
        # Detect SFINAE patterns
        self._detect_sfinae_patterns(ast_data)
        
        # Analyze C++20 concepts if present
        self._analyze_concepts(ast_data)
        
        # Detect metaprogramming patterns
        self._detect_metaprogramming_patterns(ast_data)
        
        # Build dependency graph
        template_dependencies = self._build_template_dependency_graph()
        
        # Calculate complexity metrics
        complexity_metrics = self._calculate_template_complexity()
        
        return {
            'template_declarations': [self._template_decl_to_dict(t) for t in self.template_declarations],
            'template_instantiations': [self._instantiation_to_dict(i) for i in self.template_instantiations],
            'template_specializations': self.template_specializations,
            'sfinae_patterns': [self._sfinae_to_dict(s) for s in self.sfinae_patterns],
            'concept_declarations': self.concept_declarations,
            'requires_clauses': self.requires_clauses,
            'metaprogramming_patterns': self.metaprogramming_patterns,
            'template_dependencies': template_dependencies,
            'complexity_metrics': complexity_metrics,
            'statistics': self.stats
        }
    
    def _extract_template_declarations(self, ast_data: Dict[str, Any]):
        """Extract template declarations from AST data."""
        # Extract function templates
        for func in ast_data.get('functions', []):
            if func.get('is_template'):
                template_decl = self._create_template_declaration(func, 'function_template')
                self.template_declarations.append(template_decl)
                self.template_index[template_decl.qualified_name] = template_decl
                self.stats['template_declarations'] += 1
        
        # Extract class templates
        for cls in ast_data.get('classes', []):
            if cls.get('is_template'):
                template_decl = self._create_template_declaration(cls, 'class_template')
                self.template_declarations.append(template_decl)
                self.template_index[template_decl.qualified_name] = template_decl
                self.stats['template_declarations'] += 1
        
        # Extract template specializations from template info
        for template in ast_data.get('templates', []):
            if template.get('is_specialization'):
                self._process_template_specialization(template)
    
    def _create_template_declaration(self, item: Dict[str, Any], kind: str) -> TemplateDeclaration:
        """Create a template declaration from function or class data."""
        template_info = item.get('template_info', {})
        
        # Extract template parameters
        parameters = []
        for param_info in template_info.get('parameters', []):
            param = TemplateParameter(
                name=param_info.get('name', ''),
                kind=param_info.get('kind', '').lower().replace('_parameter', ''),
                location=param_info.get('location', {}),
                is_variadic=param_info.get('is_variadic', False)
            )
            
            # Check for C++20 concept constraints
            if 'requires' in param_info.get('name', '').lower():
                param.is_concept = True
            
            parameters.append(param)
        
        template_decl = TemplateDeclaration(
            name=item['name'],
            qualified_name=item['qualified_name'],
            kind=kind,
            parameters=parameters,
            location=item['location'],
            namespace=item.get('namespace', [])
        )
        
        return template_decl
    
    def _analyze_template_instantiations(self, ast_data: Dict[str, Any]):
        """Analyze template instantiations."""
        # Look for template instantiations in calls
        for call in ast_data.get('function_calls', []):
            if call.get('is_template_instantiation'):
                instantiation = self._create_instantiation(call, 'function')
                self.template_instantiations.append(instantiation)
                self.stats['template_instantiations'] += 1
        
        # Look for template instantiations in constructor calls
        for call in ast_data.get('constructor_calls', []):
            if self._is_template_constructor(call, ast_data):
                instantiation = self._create_instantiation(call, 'constructor')
                self.template_instantiations.append(instantiation)
                self.stats['template_instantiations'] += 1
        
        # Look for template instantiations in type usage
        self._find_type_instantiations(ast_data)
    
    def _create_instantiation(self, call: Dict[str, Any], kind: str) -> TemplateInstantiation:
        """Create a template instantiation from call data."""
        template_name = call.get('function_name') or call.get('class_name', '')
        
        # Try to extract template arguments (this is challenging without full parsing)
        args = self._extract_template_args(call, kind)
        
        return TemplateInstantiation(
            template_name=template_name,
            instantiation_args=args,
            location=call.get('location', {}),
            context=call.get('caller_function', ''),
            is_explicit=self._is_explicit_instantiation(call),
            is_deduced=len(args) == 0  # If no args found, might be deduced
        )
    
    def _detect_sfinae_patterns(self, ast_data: Dict[str, Any]):
        """Detect SFINAE patterns in the code."""
        # Look for SFINAE patterns in function signatures and template parameters
        for func in ast_data.get('functions', []):
            if func.get('is_template'):
                self._analyze_function_for_sfinae(func)
        
        for cls in ast_data.get('classes', []):
            if cls.get('is_template'):
                self._analyze_class_for_sfinae(cls)
        
        # Look for SFINAE in type aliases and typedefs
        for typedef in ast_data.get('typedefs', []):
            self._analyze_typedef_for_sfinae(typedef)
    
    def _analyze_function_for_sfinae(self, func: Dict[str, Any]):
        """Analyze a function for SFINAE patterns."""
        # Check return type
        return_type = func.get('return_type', {})
        self._check_type_for_sfinae(return_type, func['location'], func['qualified_name'])
        
        # Check parameters
        for param in func.get('parameters', []):
            param_type = param.get('type', {})
            self._check_type_for_sfinae(param_type, param.get('location', {}), func['qualified_name'])
        
        # Check template parameters for requires clauses
        template_info = func.get('template_info', {})
        for param in template_info.get('parameters', []):
            if 'requires' in param.get('name', '').lower():
                self.requires_clauses.append({
                    'context': func['qualified_name'],
                    'clause': param.get('name', ''),
                    'location': param.get('location', {}),
                    'is_concept': True
                })
                self.stats['concept_uses'] += 1
    
    def _check_type_for_sfinae(self, type_info: Dict[str, Any], location: Dict[str, Any], context: str):
        """Check a type for SFINAE patterns."""
        type_spelling = type_info.get('spelling', '')
        
        for pattern_name, pattern_regex in self.sfinae_patterns.items():
            matches = re.finditer(pattern_regex, type_spelling)
            for match in matches:
                sfinae_pattern = SFINAEPattern(
                    pattern_type=pattern_name,
                    location=location,
                    condition=match.group(1) if match.groups() else match.group(0),
                    template_context=context,
                    used_traits=self._extract_type_traits(match.group(0))
                )
                
                self.sfinae_patterns.append(sfinae_pattern)
                self.stats['sfinae_uses'] += 1
    
    def _extract_type_traits(self, text: str) -> List[str]:
        """Extract type traits used in SFINAE expressions."""
        found_traits = []
        for trait in self.type_traits:
            if trait in text:
                found_traits.append(trait)
        return found_traits
    
    def _analyze_concepts(self, ast_data: Dict[str, Any]):
        """Analyze C++20 concepts if present."""
        # Look for concept declarations (would need to be added to AST parser)
        # For now, look for concept usage in template parameters
        
        for func in ast_data.get('functions', []):
            if func.get('is_template'):
                template_info = func.get('template_info', {})
                for param in template_info.get('parameters', []):
                    if self._is_concept_parameter(param):
                        self.concept_declarations.append({
                            'name': param.get('name', ''),
                            'context': func['qualified_name'],
                            'location': param.get('location', {}),
                            'constraint': param.get('constraint', '')
                        })
                        self.stats['concept_uses'] += 1
    
    def _is_concept_parameter(self, param: Dict[str, Any]) -> bool:
        """Check if a template parameter uses concepts."""
        param_name = param.get('name', '')
        # Look for concept-like naming patterns
        concept_indicators = ['concept', 'Concept', 'requires', 'Requires']
        return any(indicator in param_name for indicator in concept_indicators)
    
    def _detect_metaprogramming_patterns(self, ast_data: Dict[str, Any]):
        """Detect common metaprogramming patterns."""
        # Pattern 1: Type computation classes
        self._detect_type_computation_patterns(ast_data)
        
        # Pattern 2: Tag dispatch
        self._detect_tag_dispatch_patterns(ast_data)
        
        # Pattern 3: Expression templates
        self._detect_expression_templates(ast_data)
        
        # Pattern 4: Policy-based design
        self._detect_policy_based_design(ast_data)
        
        # Pattern 5: Curiously Recurring Template Pattern (CRTP)
        self._detect_crtp_patterns(ast_data)
    
    def _detect_type_computation_patterns(self, ast_data: Dict[str, Any]):
        """Detect type computation metaprogramming patterns."""
        for cls in ast_data.get('classes', []):
            if cls.get('is_template'):
                # Look for type member aliases
                if self._has_type_aliases(cls):
                    self.metaprogramming_patterns.append({
                        'pattern': 'type_computation',
                        'name': cls['name'],
                        'location': cls['location'],
                        'description': 'Template class used for type computation'
                    })
                    self.stats['metaprogramming_patterns'] += 1
    
    def _detect_tag_dispatch_patterns(self, ast_data: Dict[str, Any]):
        """Detect tag dispatch patterns."""
        for cls in ast_data.get('classes', []):
            if (cls['name'].endswith('_tag') or 
                cls['name'].endswith('_type') or
                cls['name'].startswith('tag_')):
                
                self.metaprogramming_patterns.append({
                    'pattern': 'tag_dispatch',
                    'name': cls['name'],
                    'location': cls['location'],
                    'description': 'Tag class for function overload resolution'
                })
                self.stats['metaprogramming_patterns'] += 1
    
    def _detect_expression_templates(self, ast_data: Dict[str, Any]):
        """Detect expression template patterns."""
        for cls in ast_data.get('classes', []):
            if cls.get('is_template'):
                # Look for operator overloads that return template types
                if self._has_expression_operators(cls, ast_data):
                    self.metaprogramming_patterns.append({
                        'pattern': 'expression_templates',
                        'name': cls['name'],
                        'location': cls['location'],
                        'description': 'Expression template for lazy evaluation'
                    })
                    self.stats['metaprogramming_patterns'] += 1
    
    def _detect_policy_based_design(self, ast_data: Dict[str, Any]):
        """Detect policy-based design patterns."""
        for cls in ast_data.get('classes', []):
            if cls.get('is_template'):
                template_params = cls.get('template_info', {}).get('parameters', [])
                # Look for multiple template parameters that might be policies
                if len(template_params) > 2:
                    policy_count = sum(1 for p in template_params 
                                     if p.get('kind') == 'type' and 
                                        ('Policy' in p.get('name', '') or
                                         'Strategy' in p.get('name', '')))
                    
                    if policy_count >= 2:
                        self.metaprogramming_patterns.append({
                            'pattern': 'policy_based_design',
                            'name': cls['name'],
                            'location': cls['location'],
                            'description': f'Policy-based design with {policy_count} policies'
                        })
                        self.stats['metaprogramming_patterns'] += 1
    
    def _detect_crtp_patterns(self, ast_data: Dict[str, Any]):
        """Detect Curiously Recurring Template Pattern."""
        for cls in ast_data.get('classes', []):
            if cls.get('is_template'):
                # Check if any base class uses this class as template parameter
                for base in cls.get('base_classes', []):
                    if cls['name'] in base.get('name', ''):
                        self.metaprogramming_patterns.append({
                            'pattern': 'CRTP',
                            'name': cls['name'],
                            'location': cls['location'],
                            'base_class': base.get('name', ''),
                            'description': 'Curiously Recurring Template Pattern'
                        })
                        self.stats['metaprogramming_patterns'] += 1
    
    def _build_template_dependency_graph(self) -> Dict[str, Any]:
        """Build template dependency graph."""
        dependencies = defaultdict(set)
        
        for instantiation in self.template_instantiations:
            template_name = instantiation.template_name
            context = instantiation.context
            
            if template_name and context:
                dependencies[context].add(template_name)
        
        return {
            'dependencies': {k: list(v) for k, v in dependencies.items()},
            'template_usage_count': dict(self.instantiation_cache),
            'most_used_templates': self._get_most_used_templates()
        }
    
    def _get_most_used_templates(self) -> List[Dict[str, Any]]:
        """Get most frequently used templates."""
        usage_count = defaultdict(int)
        
        for instantiation in self.template_instantiations:
            usage_count[instantiation.template_name] += 1
        
        # Sort by usage count
        sorted_templates = sorted(usage_count.items(), key=lambda x: x[1], reverse=True)
        
        return [
            {'template_name': name, 'usage_count': count}
            for name, count in sorted_templates[:10]  # Top 10
        ]
    
    def _calculate_template_complexity(self) -> Dict[str, Any]:
        """Calculate template complexity metrics."""
        complexity_metrics = {
            'average_template_parameters': 0,
            'max_template_parameters': 0,
            'nested_template_depth': 0,
            'sfinae_complexity': 0,
            'template_specialization_ratio': 0,
            'concept_usage_ratio': 0
        }
        
        if not self.template_declarations:
            return complexity_metrics
        
        # Calculate average and max template parameters
        param_counts = [len(t.parameters) for t in self.template_declarations]
        complexity_metrics['average_template_parameters'] = sum(param_counts) / len(param_counts)
        complexity_metrics['max_template_parameters'] = max(param_counts) if param_counts else 0
        
        # Calculate SFINAE complexity (number of SFINAE patterns per template)
        complexity_metrics['sfinae_complexity'] = len(self.sfinae_patterns) / len(self.template_declarations)
        
        # Calculate specialization ratio
        total_templates = len(self.template_declarations)
        specialized_templates = len(self.template_specializations)
        complexity_metrics['template_specialization_ratio'] = specialized_templates / total_templates if total_templates > 0 else 0
        
        # Calculate concept usage ratio
        concept_templates = sum(1 for t in self.template_declarations if any(p.is_concept for p in t.parameters))
        complexity_metrics['concept_usage_ratio'] = concept_templates / total_templates if total_templates > 0 else 0
        
        return complexity_metrics
    
    # Helper methods for pattern detection
    
    def _has_type_aliases(self, cls: Dict[str, Any]) -> bool:
        """Check if class has type member aliases."""
        # This would need more detailed AST information
        # For now, check if class name suggests type computation
        type_computation_names = ['type_traits', 'meta', 'type_', '_type', 'traits']
        return any(pattern in cls['name'].lower() for pattern in type_computation_names)
    
    def _has_expression_operators(self, cls: Dict[str, Any], ast_data: Dict[str, Any]) -> bool:
        """Check if class has operator overloads for expression templates."""
        # Look for operator overloads in the class
        # This would need method information from AST
        return False  # Placeholder
    
    def _is_template_constructor(self, call: Dict[str, Any], ast_data: Dict[str, Any]) -> bool:
        """Check if constructor call is for a template class."""
        class_name = call.get('class_name', '')
        for cls in ast_data.get('classes', []):
            if cls['name'] == class_name and cls.get('is_template'):
                return True
        return False
    
    def _find_type_instantiations(self, ast_data: Dict[str, Any]):
        """Find template instantiations in type usage."""
        # Look through variables and function parameters for template types
        for var in ast_data.get('variables', []):
            type_spelling = var.get('type', {}).get('spelling', '')
            if '<' in type_spelling and '>' in type_spelling:
                # Potential template instantiation
                template_name, args = self._parse_template_type(type_spelling)
                if template_name:
                    instantiation = TemplateInstantiation(
                        template_name=template_name,
                        instantiation_args=args,
                        location=var.get('location', {}),
                        context=var.get('qualified_name', ''),
                        instantiation_kind='type_usage'
                    )
                    self.template_instantiations.append(instantiation)
                    self.stats['template_instantiations'] += 1
    
    def _parse_template_type(self, type_spelling: str) -> Tuple[Optional[str], List[str]]:
        """Parse template type string to extract template name and arguments."""
        match = re.match(r'([^<]+)<([^>]+)>', type_spelling)
        if match:
            template_name = match.group(1).strip()
            args_str = match.group(2)
            # Simple argument parsing (would need more sophisticated parsing for nested templates)
            args = [arg.strip() for arg in args_str.split(',')]
            return template_name, args
        return None, []
    
    def _extract_template_args(self, call: Dict[str, Any], kind: str) -> List[str]:
        """Extract template arguments from call data."""
        # This is challenging without more detailed AST information
        # For now, return empty list
        return []
    
    def _is_explicit_instantiation(self, call: Dict[str, Any]) -> bool:
        """Check if this is an explicit template instantiation."""
        # Look for explicit template syntax in the call
        return False  # Placeholder
    
    def _process_template_specialization(self, template: Dict[str, Any]):
        """Process template specialization information."""
        specialization = {
            'template_name': template.get('name', ''),
            'specialization_args': template.get('specialization_args', []),
            'location': template.get('location', {}),
            'is_partial': template.get('is_partial_specialization', False)
        }
        
        self.template_specializations.append(specialization)
        
        if specialization['is_partial']:
            self.stats['partial_specializations'] += 1
        else:
            self.stats['explicit_specializations'] += 1
    
    def _analyze_class_for_sfinae(self, cls: Dict[str, Any]):
        """Analyze a class for SFINAE patterns."""
        # Check base classes for SFINAE
        for base in cls.get('base_classes', []):
            self._check_type_for_sfinae(
                {'spelling': base.get('name', '')}, 
                base.get('location', {}), 
                cls['qualified_name']
            )
    
    def _analyze_typedef_for_sfinae(self, typedef: Dict[str, Any]):
        """Analyze typedef for SFINAE patterns."""
        underlying_type = typedef.get('underlying_type', {})
        self._check_type_for_sfinae(
            underlying_type, 
            typedef.get('location', {}), 
            typedef.get('qualified_name', '')
        )
    
    # Conversion methods for output
    
    def _template_decl_to_dict(self, template: TemplateDeclaration) -> Dict[str, Any]:
        """Convert template declaration to dictionary."""
        return {
            'name': template.name,
            'qualified_name': template.qualified_name,
            'kind': template.kind,
            'parameters': [self._param_to_dict(p) for p in template.parameters],
            'location': template.location,
            'namespace': template.namespace,
            'specializations': template.specializations,
            'instantiations': template.instantiations,
            'is_partial_specialization': template.is_partial_specialization,
            'requires_clause': template.requires_clause
        }
    
    def _param_to_dict(self, param: TemplateParameter) -> Dict[str, Any]:
        """Convert template parameter to dictionary."""
        return {
            'name': param.name,
            'kind': param.kind,
            'default_value': param.default_value,
            'constraints': param.constraints,
            'location': param.location,
            'is_variadic': param.is_variadic,
            'is_concept': param.is_concept
        }
    
    def _instantiation_to_dict(self, instantiation: TemplateInstantiation) -> Dict[str, Any]:
        """Convert template instantiation to dictionary."""
        return {
            'template_name': instantiation.template_name,
            'instantiation_args': instantiation.instantiation_args,
            'location': instantiation.location,
            'context': instantiation.context,
            'is_explicit': instantiation.is_explicit,
            'is_deduced': instantiation.is_deduced,
            'instantiation_kind': instantiation.instantiation_kind
        }
    
    def _sfinae_to_dict(self, sfinae: SFINAEPattern) -> Dict[str, Any]:
        """Convert SFINAE pattern to dictionary."""
        return {
            'pattern_type': sfinae.pattern_type,
            'location': sfinae.location,
            'condition': sfinae.condition,
            'template_context': sfinae.template_context,
            'used_traits': sfinae.used_traits
        }